﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text.RegularExpressions;

namespace FredBot
{
    public class LogLineParser : IDisposable
    {
        private static readonly Regex Comment = new Regex(@"^//");
        private static readonly Regex Events_57_58_59_66 = new Regex("^\"(?<Player>[^\"]+)\" (?<Event1>[^\"\\(]+) \"(?<Noun1>[^\"]+)\" (?<Event2>[^\"\\(]+) \"(?<Noun2>[^\"]+)\"(?<Properties>.*)$", RegexOptions.Compiled);
        private static readonly Regex Events_50_53_56_60_63_68_69 = new Regex("^\"(?<Player>[^\"]+)\" (?<Event1>[^\"\\(]+) \"(?<Noun1>[^\"]+)\"(?<Properties>.*)$", RegexOptions.Compiled);
        private static readonly Regex Events_50_51_52 = new Regex("^\"(?<Player>[^\"]+)\" (?<Event1>[^\\(]+)(?<Properties>.*)$", RegexOptions.Compiled);
        private static readonly Regex Events_61_64 = new Regex("^Team \"(?<Team>[^\"]+)\" (?<Event1>[^\"\\(]+) \"(?<Noun1>[^\"]+)\"( (?<Event2>[^\"\\(]+) \"(?<Noun2>[^\"]+)\" (?<Event3>[^\"\\(]+))?(?<Properties>.*)$", RegexOptions.Compiled);
        private static readonly Regex Events_62_3_5_6 = new Regex("^(?<Event1>[^\"\\(]+) \"(?<Noun1>[^\"]+)\"( (?<Event2>[^\"\\(]+) \"(?<Noun2>[^\"]+)\")?(?<Properties>.*)$", RegexOptions.Compiled);
        private static readonly Regex PlayerInfo = new Regex(@"^(?<Name>.+)<(?<Userid>\d+)><(?<Steamid>STEAM_\d:\d:\d+)><(?<Team>.*)>$", RegexOptions.Compiled);
        private static readonly Regex Properties = new Regex("\\s*\\((?<Key>\\S+)(?: \"(?<Value>[^\"]+)\")?\\)", RegexOptions.Compiled);

        private GameState State;
        private TFDataEntities context;
        private bool disposed = false;

        public LogLineParser(string serverIP)
        {
            context = new TFDataEntities();
            State = new GameState(serverIP, context);
        }

        public void ParseLine(string p, DateTime date)
        {
            LogLineData data = new LogLineData(date);
            if (ParseComment(p, ref data))
            {
                return;
            }
            else if (ParsePlayerPlayerEvent(p, ref data))
            {
                PlayerInfo affectedPlayer = ParsePlayer(data.Noun1);
                if (affectedPlayer == null)
                {
                    affectedPlayer = ParsePlayer(data.Noun2);
                    if(affectedPlayer != null)
                        data.Noun2 = affectedPlayer.SteamID;
                }
                else
                {
                    data.Noun1 = affectedPlayer.SteamID;
                }
                State.RecordPlayerPlayerEvent(data, ParsePlayer(data.Player));
            }
            else if (ParsePlayerEvent(p, ref data))
            {
                if (data.Event1.Equals("changed role to") && !State.IsMatchStarted)
                {
                    State.AddPlayer(ParsePlayer(data.Player), data.Noun1);
                }
                else if (data.Event1.Equals("joined team") && !State.IsMatchStarted)
                {
                    State.UpdatePlayerTeam(ParsePlayer(data.Player), data.Noun1);
                }
                else
                {
                    State.RecordPlayerEvent(data, ParsePlayer(data.Player));
                }
            }
            else if (ParsePlayerConnectEvent(p, ref data))
            {
                if (data.Event1.Equals("entered the game"))
                {
                    PlayerInfo pi = ParsePlayer(data.Player);
                    if (pi != null)
                    {
                        var steamPlayerQry = from player in context.Players
                                             where player.SteamID.Equals(pi.SteamID)
                                             select player;

                        Player thePlayer = steamPlayerQry.FirstOrDefault();
                        if (thePlayer == null)
                        {
                            thePlayer = new Player();
                            thePlayer.SteamID = pi.SteamID;
                            thePlayer.Name = pi.Name;
                            context.Players.AddObject(thePlayer);
                        }
                        else
                        {
                            thePlayer.Name = pi.Name;
                            context.Players.ApplyCurrentValues(thePlayer);
                        }
                        context.SaveChanges();
                    }
                }
                else if (data.Event1.Equals("disconnected "))
                {
                    State.RemovePlayer(data, ParsePlayer(data.Player));
                }
            }
            else if (ParseTeamEvent(p, ref data))
            {
                State.RecordTeamEvent(data);
            }
            else if (ParseWorldEvent(p, ref data))
            {
                if (data.Event1.Equals("Started map"))
                {
                    State.Reset(data);
                }
                else
                {
                    if (!State.IsMatchStarted)
                    {
                        if (data.Event1.Equals("World triggered") && data.Noun1.Equals("Round_Start"))
                        {
                            State.StartMatch(data);
                        }
                    }
                    else
                    {
                        if (data.Event1.Equals("World triggered") && data.Noun1.Equals("Game_Over"))
                        {
                            State.EndMatch(data);
                        }
                    }
                    State.RecordWorldEvent(data);
                }
            }
        }

        public bool ParseComment(string p, ref LogLineData d)
        {
            if (Comment.IsMatch(p))
            {
                return true;
            }

            return false;
        }

        public PlayerInfo ParsePlayer(string p)
        {
            System.Text.RegularExpressions.Match match = PlayerInfo.Match(p);
            if (match.Success)
            {
                PlayerInfo pi = new PlayerInfo();
                pi.Name = match.Groups["Name"].Value;
                pi.Uid = match.Groups["Userid"].Value;
                pi.SteamID = match.Groups["Steamid"].Value;
                pi.Team = match.Groups["Team"].Value;

                return pi;
            }

            return null;
        }

        public bool ParsePlayerPlayerEvent(string p, ref LogLineData d)
        {
            return Parse_Events_57_58_59_66(p, ref d);
        }

        private bool Parse_Events_57_58_59_66(string p, ref LogLineData d)
        {
            System.Text.RegularExpressions.Match match = Events_57_58_59_66.Match(p);
            if (match.Success)
            {
                d.Player = match.Groups["Player"].Value;
                d.Event1 = match.Groups["Event1"].Value;
                d.Noun1 = match.Groups["Noun1"].Value;
                d.Event2 = match.Groups["Event2"].Value;
                d.Noun2 = match.Groups["Noun2"].Value;
                d.Properties = ParseProperties(match.Groups["Properties"].Value);

                return true;
            }

            return false;
        }

        public bool ParsePlayerEvent(string p, ref LogLineData d)
        {
            return Parse_Events_50_53_56_60_63_68_69(p, ref d);
        }

        private bool Parse_Events_50_53_56_60_63_68_69(string p, ref LogLineData d)
        {
            System.Text.RegularExpressions.Match match = Events_50_53_56_60_63_68_69.Match(p);
            if (match.Success)
            {
                d.Player = match.Groups["Player"].Value;
                d.Event1 = match.Groups["Event1"].Value;
                d.Noun1 = match.Groups["Noun1"].Value;
                d.Properties = ParseProperties(match.Groups["Properties"].Value);

                return true;
            }

            return false;
        }

        public bool ParsePlayerConnectEvent(string p, ref LogLineData d)
        {
            return Parse_Events_50_51_52(p, ref d);
        }

        private bool Parse_Events_50_51_52(string p, ref LogLineData d)
        {
            System.Text.RegularExpressions.Match match = Events_50_51_52.Match(p);
            if (match.Success)
            {
                d.Player = match.Groups["Player"].Value;
                d.Event1 = match.Groups["Event1"].Value;
                d.Properties = ParseProperties(match.Groups["Properties"].Value);

                return true;
            }

            return false;
        }

        public bool ParseTeamEvent(string p, ref LogLineData d)
        {
            return Parse_Events_61_64(p, ref d);
        }

        private bool Parse_Events_61_64(string p, ref LogLineData d)
        {
            System.Text.RegularExpressions.Match match = Events_61_64.Match(p);
            if (match.Success)
            {
                d.Team = match.Groups["Team"].Value;
                d.Event1 = match.Groups["Event1"].Value;
                d.Noun1 = match.Groups["Noun1"].Value;
                d.Event2 = match.Groups["Event2"].Value;
                d.Noun2 = match.Groups["Noun2"].Value;
                d.Event3 = match.Groups["Event3"].Value;
                d.Properties = ParseProperties(match.Groups["Properties"].Value);

                return true;
            }

            return false;
        }

        public bool ParseWorldEvent(string p, ref LogLineData d)
        {
            return Parse_Events_62_3_5_6(p, ref d);
        }

        private bool Parse_Events_62_3_5_6(string p, ref LogLineData d)
        {
            System.Text.RegularExpressions.Match match = Events_62_3_5_6.Match(p);
            if (match.Success)
            {
                d.Event1 = match.Groups["Event1"].Value;
                d.Noun1 = match.Groups["Noun1"].Value;
                d.Event2 = match.Groups["Event2"].Value;
                d.Noun2 = match.Groups["Noun2"].Value;
                d.Properties = ParseProperties(match.Groups["Properties"].Value);

                return true;
            }

            return false;
        }

        public Dictionary<string, string> ParseProperties(string props)
        {
            Dictionary<string, string> propsMap = new Dictionary<string, string>();
            MatchCollection propMatches = Properties.Matches(props);
            foreach (System.Text.RegularExpressions.Match m in propMatches)
            {
                PlayerInfo pi = ParsePlayer(m.Groups["Value"].Value);
                if (pi != null)
                {
                    propsMap.Add(m.Groups["Key"].Value, pi.SteamID);
                }
                else
                {
                    propsMap.Add(m.Groups["Key"].Value, m.Groups["Value"].Value);
                }
            }

            return propsMap;
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    State.Dispose();
                    context.Dispose();
                }
                disposed = true;
            }
        }

    }
}